# coding=utf-8

from flask import Flask
import logging
import os
from concurrent_log_handler import ConcurrentRotatingFileHandler
from queue import Queue
from logging.handlers import QueueHandler, QueueListener
import click

from app.config import Config, proj_dir
from app.extensions import (db, migrate, login_manager, mail, csrf, bootstrap, session_id_manager,
                            socketio, dispatcher_scheduler)
from app import models
from app.template_global import (render_to_json, sort_by_order_in_module, sort_by_order_in_logic_controller,
                                 can_show, is_forbidden, get_latest_reports, get_case_from_id, get_tool_from_id,
                                 calc_percent, url_for_static)
from app.cores.ws import register_all_user_socket


def create_app(config_class=Config):
    app = Flask(import_name=__name__)
    app.config.from_object(obj=config_class)

    register_logging(app)
    register_extensions(app)
    register_blueprints(app)
    register_shell_context(app)
    register_template_global(app)
    register_template_context_processor(app)
    register_before_first_request_funcs(app)
    register_cli(app)

    return app


def register_blueprints(app: Flask):
    from app.routes.auth import bp as auth_bp
    app.register_blueprint(blueprint=auth_bp, url_prefix='/auth')
    from app.routes.main import bp as main_bp
    app.register_blueprint(blueprint=main_bp)
    from app.routes.case import bp as case_bp
    app.register_blueprint(blueprint=case_bp, url_prefix='/case')
    from app.routes.project import bp as project_bp
    app.register_blueprint(blueprint=project_bp, url_prefix='/project')
    from app.routes.module import bp as module_bp
    app.register_blueprint(blueprint=module_bp, url_prefix='/module')
    from app.routes.logic_controller import bp as logic_controller_bp
    app.register_blueprint(blueprint=logic_controller_bp, url_prefix='/logic_controller')
    from app.routes.scene import bp as scene_bp
    app.register_blueprint(blueprint=scene_bp, url_prefix='/scene')
    from app.routes.report import bp as report_bp
    app.register_blueprint(blueprint=report_bp, url_prefix='/report')
    from app.routes.setting import bp as settings_bp
    app.register_blueprint(blueprint=settings_bp)
    from app.routes.error import bp as errors_bp
    app.register_blueprint(blueprint=errors_bp, url_prefix='/error')
    from app.routes.ajax import bp as ajax_bp
    app.register_blueprint(blueprint=ajax_bp, url_prefix='/ajax')
    from app.utils import bp as util_bp
    app.register_blueprint(blueprint=util_bp, url_prefix='/util')


def register_extensions(app: Flask):
    db.init_app(app=app)
    migrate.init_app(app=app, db=db)
    login_manager.init_app(app=app)
    mail.init_app(app=app)
    csrf.init_app(app=app)
    bootstrap.init_app(app=app)
    socketio.init_app(app=app)
    session_id_manager.init_app(app=app)
    dispatcher_scheduler.init_app(app=app)


def register_shell_context(app: Flask):
    from app.models import (db, User, EmailSetting, Case, HTTPCase, HTTPCaseParameter, HTTPCaseExpectation,
                            HTTPCaseFileUpload, Project, Module, Scene, Dispatcher, DispatcherDetail, Report,
                            ReportCaseData, ReportCaseExpectationData, SubElementInLogicController,
                            ReportToolData, ReportScriptToolData, ReportTimerToolData, ReportVariableDefinitionToolData,
                            ReportVariableDefinitionToolListData, Tool)
    from app.email import send_email
    from app.utils.util import get_project_id_from_current_element

    @app.shell_context_processor
    def make_shell_context():
        return {
            'db': db,
            'User': User,
            'send_email': send_email,
            'EmailSetting': EmailSetting,
            'Case': Case,
            'HTTPCase': HTTPCase,
            'HTTPCaseParameter': HTTPCaseParameter,
            'HTTPCaseExpectation': HTTPCaseExpectation,
            'HTTPCaseFileUpload': HTTPCaseFileUpload,
            'Project': Project,
            'Module': Module,
            'Scene': Scene,
            'Tool': Tool,
            'Dispatcher': Dispatcher,
            'DispatcherDetail': DispatcherDetail,
            'SubElementInLogicController': SubElementInLogicController,
            'ReportToolData': ReportToolData,
            'ReportScriptToolData': ReportScriptToolData,
            'ReportTimerToolData': ReportTimerToolData,
            'ReportVariableDefinitionToolData': ReportVariableDefinitionToolData,
            'ReportVariableDefinitionToolListData': ReportVariableDefinitionToolListData,
            'get_project_id_from_current_element': get_project_id_from_current_element,
        }


def register_logging(app: Flask):
    # 日志记录
    if not os.path.exists(os.path.join(proj_dir, 'logs')):
        os.mkdir(os.path.join(proj_dir, 'logs'))
    # 改用ConcurrentRotatingFileHandler避免出现多进程时出现文件争用情况
    file_handler = ConcurrentRotatingFileHandler(filename=os.path.join(proj_dir, 'logs', 'webserver.log'),
                                                 maxBytes=1024 * 1024 * 10,
                                                 backupCount=10)
    file_sys_handler = ConcurrentRotatingFileHandler(filename=os.path.join(proj_dir, 'logs', 'webserver_sys.log'),
                                                     maxBytes=1024 * 1024 * 10,
                                                     backupCount=10)
    stream_handler = logging.StreamHandler()
    formater = logging.Formatter(
        '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
    )
    file_handler.setFormatter(formater)
    file_sys_handler.setFormatter(formater)
    stream_handler.setFormatter(formater)
    file_handler.setLevel(logging.INFO)
    file_sys_handler.setLevel(logging.DEBUG)
    stream_handler.setLevel(logging.DEBUG)

    logger_queue = Queue(-1)
    logger_queue_handler = QueueHandler(logger_queue)
    logger_queue_listener = QueueListener(logger_queue,
                                          file_sys_handler, file_handler, stream_handler,
                                          respect_handler_level=True)
    logger_queue_listener.start()

    # app.logger
    for handler in app.logger.handlers:
        app.logger.removeHandler(handler)
    app.logger.addHandler(logger_queue_handler)
    app.logger.setLevel(logging.INFO)
    app.logger.propagate = False  # 记录消息将不会传递给当前记录器的祖先记录器的处理器
    app.logger.info("WebServer 服务已启动")

    # 框架默认logger指定输出handler(包含了http请求日志、定时任务job日志、socket日志)
    logger = logging.getLogger()
    logger.addHandler(logger_queue_handler)
    logger.setLevel(logging.DEBUG)


def register_template_global(app: Flask):
    # 注册jinja全局函数
    app.add_template_global(render_to_json, 'render_to_json')
    app.add_template_global(sort_by_order_in_module, 'sort_by_order_in_module')
    app.add_template_global(can_show, 'can_show')
    app.add_template_global(sort_by_order_in_logic_controller, 'sort_by_order_in_logic_controller')
    app.add_template_global(get_latest_reports, 'get_latest_reports')
    app.add_template_global(is_forbidden, 'is_forbidden')
    app.add_template_global(get_case_from_id, 'get_case_from_id')
    app.add_template_global(get_tool_from_id, 'get_tool_from_id')
    app.add_template_global(calc_percent, 'calc_percent')
    app.add_template_global(url_for_static, 'url_for_static')


def register_template_context_processor(app: Flask):
    # 注册jinja模板上下文
    @app.context_processor
    def context_processor():
        return {
            'version': app.config['AAT_VERSION'],
        }


def register_before_first_request_funcs(app: Flask):
    # 在app实例拉起后，第一个请求处理前进行一些准备工作
    # 注册所有用户socket
    app.before_first_request_funcs.append(register_all_user_socket)


def register_cli(app: Flask):
    """注册命令"""
    @app.cli.command(short_help='初始化当前项目.')
    def init():
        from flask_migrate import init, migrate, upgrade
        click.echo('[flask init] 开始初始化数据库.')
        click.echo('[flask init] init.')
        init()
        click.echo('[flask init] migrate.')
        migrate(message='init db')
        click.echo('[flask init] upgrade.')
        upgrade()
        click.echo('[flask init] add user admin.')
        from app.models import User
        User.add(username='admin', password='admin', email='admin@example.com')
        click.echo('[flask init] 已完成数据库初始化.')

    @app.cli.command(short_help='测试邮件服务.')
    @click.option('--receiver', '-r', help='收件人邮箱.')
    def email_test(receiver: str):
        from app.email import send_email
        import traceback
        if receiver is None or receiver.strip() == '':
            click.echo('[flask email-test] 请输入收件人邮箱 eg: flask email-test -r zhangsan@test.com')
            return
        click.echo('[flask email-test] 开始测试email服务.')
        click.echo('[flask email-test] 收件人邮箱: %s.' % receiver)
        try:
            send_email(
                subject="[ApiAutomationTest] 通知",
                sender=app.config['ADMINS'][0],
                recipients=[receiver],
                text_body='您已收到来自ApiAutomationTest的邮件.',
                async_send=False,
            )
        except Exception:
            click.echo('[flask email-test] 邮件发送失败，失败原因: \n%s' % traceback.format_exc())
        else:
            click.echo('[flask email-test] 测试邮件已发送，请查看收件邮箱.')
